package com.site.common.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.site.common.R;
import com.site.common.annotation.AccessLimit;
import com.site.common.annotation.Login;
import com.site.common.annotation.EncryRSA;
import com.site.common.annotation.ParamXssPass;
import com.site.common.exception.BusinessException;
import com.site.common.filter.handler.AccessLimitFilter;
import com.site.common.filter.handler.EntryRequestFilter;
import com.site.common.filter.handler.LoginFilter;
import com.site.common.filter.handler.ParamXssPassFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;

/**
 * 自定义过滤器
 */
public class RequestFilter extends OncePerRequestFilter {

    private final Set<String> excludes;
    private final AntPathMatcher antPathMatcher;

    /**
     * 上下文
     */
    private ApplicationContext ctx;

    public RequestFilter(Set<String> excludes,AntPathMatcher antPathMatcher){
        this.excludes = excludes;
        this.antPathMatcher = antPathMatcher;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String url = request.getServletPath();
        try {
            if (excludes.stream().noneMatch(e -> antPathMatcher.match(e,url))){
                setApplicationContext(request);
                HandlerMethod handlerMethod = getHandlerMethod(request);
                if (handlerMethod != null){
                    // 是否登录
                    if (handlerMethod.hasMethodAnnotation(Login.class)){
                        doInstanceFilter(LoginFilter.class).doHandlerFilter(request,response,handlerMethod,ctx);
                    }
                    // 是否XSS攻击
                    if (handlerMethod.hasMethodAnnotation(ParamXssPass.class)){
                        request = (HttpServletRequest) doInstanceFilter(ParamXssPassFilter.class).doHandlerFilter(request,response,handlerMethod,ctx);
                    }
                    // 是否限流
                    if (handlerMethod.hasMethodAnnotation(AccessLimit.class)){
                        doInstanceFilter(AccessLimitFilter.class).doHandlerFilter(request,response,handlerMethod,ctx);
                    }
                    // 是否RSA加密
                    if (handlerMethod.hasMethodAnnotation(EncryRSA.class)){
                        request = (HttpServletRequest) doInstanceFilter(EntryRequestFilter.class).doHandlerFilter(request,response,handlerMethod,ctx);
                    }
                }
            }
        }catch (Throwable e){
            //throw new RuntimeException(e.getLocalizedMessage());
            if (e.getCause() instanceof BusinessException){
                BusinessException ex = (BusinessException) e.getCause();
                logger.error("业务异常，{}",ex);
                responseJson(response, R.error(ex.getCode(),ex.getMessage()));
                return;
            }
            logger.error("系统出错，{}",e);
            responseJson(response, R.error(e.getMessage()));
            return;
        }
        filterChain.doFilter(request,response);
    }

    /**
     * 获取实例
     * @param clazz
     * @return
     */
    private BaseFilter<?> doInstanceFilter(Class<?> clazz){
        return (BaseFilter<?>)ctx.getBean(clazz);
    }

    /**
     * 设置上下文
     * @param request
     */
    private static final byte[] lock = new byte[0];
    private void setApplicationContext(HttpServletRequest request){
        if (ctx == null){
            synchronized (lock){
                if (ctx == null){
                    ctx = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
                }
            }
        }
    }

    /**
     * 获取请求具体映射
     * @param request
     * @return
     * @throws Exception
     */
    private HandlerMethod getHandlerMethod(HttpServletRequest request) throws Exception {
        RequestMappingHandlerMapping requestMappingHandlerMapping = ctx.getBean(RequestMappingHandlerMapping.class);
        HandlerExecutionChain handlerExecutionChain = null;
        try {
            handlerExecutionChain = requestMappingHandlerMapping.getHandler(request);
        } catch (Exception e) {
            throw e;
        }
        if (handlerExecutionChain != null){
            return (HandlerMethod) handlerExecutionChain.getHandler();
        }
        return null;
    }

    /**
     * 输出json
     * @param response
     * @param data
     */
    private void responseJson(HttpServletResponse response,Object data){
        response.setContentType("application/json;charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        try {
            PrintWriter writer = response.getWriter();
            // 消除对同一对象循环引用的问题，默认为false，不设置 SerializerFeature.DisableCircularReferenceDetect 数据可能会乱码
            writer.write(JSON.toJSONString(data, SerializerFeature.DisableCircularReferenceDetect));
            writer.close();
            response.flushBuffer();
        } catch (IOException e) {
            logger.error("【输出 JSON 异常】，{}",e);
        }
    }
}
